<?php

namespace maotool\orm\mapper;

use maotool\log\Console;
use maotool\object\ReflectionUtil;
use maotool\object\StrUtil;
use maotool\script\PhpScriptUtil;
use maotool\util\ArrayUtil;

/**
 * 处理标签
 */
class MapperTagProcessor
{

    /**
     * 加载元素
     * @param $element
     * @param $params
     * @return array
     */
    public function loadElement($element, $params)
    {
        if ($element["nodeName"] == "#text") {
            $nodeName = "sharpText";
        } else {
            $nodeName = $element["nodeName"];
        }
        $nodeName = "tag" . StrUtil::upperFirst($nodeName);
        return call_user_func(array($this, $nodeName), $element, $params);
    }

    /**
     * 遍历元素
     * @param $childNodes
     * @param $params
     * @return array
     */
    public function loadNodes($childNodes, $params)
    {
        $sql = [];
        $vars = [];
        foreach ($childNodes as $childNode) {
            $element = $this->loadElement($childNode, $params);
            $sql[] = $element["sql"];
            $vars = ArrayUtil::merge($vars, $element["vars"]);
        }
        return ["sql" => implode("", $sql), "vars" => $vars];
    }

    /**
     * 加载替换Sql中的参数
     * @param $sql
     * @param $params
     * @return array
     */
    private function loadVar($sql, $params)
    {
        $vars = [];
        $sql = StrUtil::format($sql, $params, function ($match, $params) use (&$vars) {
            $vars[] = PhpScriptUtil::expression($match, $params);
            return "?";
        });
        $sql = StrUtil::format($sql, $params, function ($match, $params) use (&$vars) {
            return PhpScriptUtil::expression($match, $params);
        }, '\${', "}");
        return ["sql" => $sql, "vars" => $vars];
    }

    private function loadParam($element, $params)
    {
        if (isset($element["attributes"]["parameters"])) {
            $methodParams = [];
            $paramDefaults = PhpScriptUtil::methodParams($element["attributes"]["parameters"]);
            for ($i = 0; $i < count($paramDefaults); $i++) {
                $paramDefault = PhpScriptUtil::methodParam($element["attributes"]["parameters"],
                    $paramDefaults[$i]);
                if ($i < count($params)) {
                    $methodParams[$paramDefaults[$i]] = $params[$i];
                } else {
                    $methodParams[$paramDefaults[$i]] = $paramDefault["default"];
                }
            }
            $params = $methodParams;
        }
        return $params;
    }

    public function tagSelect($element, $params)
    {
        $result = $this->loadNodes($element["childNodes"], $this->loadParam($element, $params));
        if (isset($element["attributes"]["resultType"])) {
            $result["resultType"] = $element["attributes"]["resultType"];
        }
        if (isset($element["attributes"]["id"])) {
            $result["methodName"] = $element["attributes"]["id"];
        }
        $result["type"] = "select";
        return $result;
    }

    public function tagInsert($element, $params)
    {
        $result = $this->loadNodes($element["childNodes"], $this->loadParam($element, $params));
        if (isset($element["attributes"]["useGeneratedKeys"])) {
            $result["useGeneratedKeys"] = $element["attributes"]["useGeneratedKeys"];
        }
        if (isset($element["attributes"]["keyProperty"])) {
            $result["keyProperty"] = $element["attributes"]["keyProperty"];
        }
        if (isset($element["attributes"]["id"])) {
            $result["methodName"] = $element["attributes"]["id"];
        }
        $result["type"] = "insert";
        return $result;
    }

    public function tagUpdate($element, $params)
    {
        $result = $this->loadNodes($element["childNodes"], $this->loadParam($element, $params));
        if (isset($element["attributes"]["id"])) {
            $result["methodName"] = $element["attributes"]["id"];
        }
        $result["type"] = "update";
        return $result;
    }

    public function tagForeach($element, $params)
    {
        $sql = [];
        $vars = [];
        $collection = PhpScriptUtil::expression($element["attributes"]["collection"], $params);
        foreach ($collection as $key => $value) {
            $params[StrUtil::replace("$", "", $element["attributes"]["item"])] = $value;
            if (StrUtil::hasNotEmpty($element["attributes"]["index"])) {
                $params[StrUtil::replace("$", "", $element["attributes"]["index"])] = $key;
            }
            $result = $this->loadNodes($element["childNodes"], $params);
            $sql[] = $result["sql"];
            $vars = ArrayUtil::merge($vars, $result["vars"]);
        }
        if (StrUtil::hasNotEmpty($element["attributes"]["separator"])) {
            $separator = $element["attributes"]["separator"];
        } else {
            $separator = "";
        }
        return ["sql" => implode($separator, $sql), "vars" => $vars];
    }

    public function tagWhere($element, $params)
    {
        $result = $this->loadNodes($element["childNodes"], $params);
        $result["sql"] = preg_replace("/^\s*[Aa][Nn][Dd]/", "", $result["sql"]);
        $result["sql"] = preg_replace("/^\s*[Oo][Rr]/", "", $result["sql"]);
        if (strlen(preg_replace("/\s/", "", $result["sql"])) > 0) {
            $result["sql"] = "where " . $result["sql"];
            return $result;
        } else {
            return ["sql" => "", "vars" => []];
        }
    }

    public function tagChoose($element, $params)
    {
        $sql = [];
        $vars = [];
        $bool = false;
        foreach ($element["childNodes"] as $childNode) {
            if ($childNode["nodeName"] == "when") {
                if ($bool === true) {
                    continue;
                }
                if (PhpScriptUtil::expression($childNode["attributes"]["test"], $params)) {
                    $result = $this->loadNodes($childNode["childNodes"], $params);
                    $sql[] = $result["sql"];
                    $vars = ArrayUtil::merge($vars, $result["vars"]);
                    $bool = true;
                }
            } else if ($childNode["nodeName"] == "otherwise") {
                if ($bool === true) {
                    continue;
                }
                $result = $this->loadNodes($childNode["childNodes"], $params);
                $sql[] = $result["sql"];
                $vars = ArrayUtil::merge($vars, $result["vars"]);
                $bool = true;
            } else {
                $element = $this->loadElement($childNode, $params);
                $sql[] = $element["sql"];
                $vars = ArrayUtil::merge($vars, $element["vars"]);
            }
        }
        return ["sql" => implode("", $sql), "vars" => $vars];
    }

    public function tagSet($element, $params)
    {
        $result = $this->loadNodes($element["childNodes"], $params);
        $result["sql"] = preg_replace("/^\s*[Ss][Ee][Tt]/", "", $result["sql"]);
        $result["sql"] = preg_replace("/^\s*,/", "", $result["sql"]);
        $result["sql"] = "set " . $result["sql"];
        return $result;
    }

    public function tagIf($element, $params)
    {
        if (PhpScriptUtil::expression($element["attributes"]["test"], $params)) {
            return $this->loadNodes($element["childNodes"], $params);
        } else {
            return ["sql" => "", "vars" => []];
        }
    }

    public function tagSharpText($element, $params)
    {
        return $this->loadVar($element["nodeValue"], $params);
    }

}